package Math::Calculus::Bisection;

use strict;
use warnings;

use Moose;
use Math::Calculus::Expression;

our $VERSION = '0.01';

#TODO reclamar se a exp tiver mais de uma var
has expression => ( isa => 'Str', is => 'rw', required => 1 );

#TODO reclamar se a variável não corresponder à da exp
has variable  => ( isa => 'Str', is => 'rw', required => 1 );
has start     => ( isa => 'Num', is => 'rw', required => 1 );
has end       => ( isa => 'Num', is => 'rw', required => 1 );
has precision => ( isa => 'Int', is => 'rw', required => 1 );
has partial_results => ( is => 'rw', default => sub { [] } );

sub BUILD {
    my $self = shift;
    $self->validate;
}

sub _log10 {
    my $n = shift;
    return log($n) / log(10);
}

sub validate {
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( my $var = $self->variable ) or die $exp->getError;
    $exp->setExpression( $self->expression );
    my ( $start, $end, $e ) = ( $self->start, $self->end, $self->precision );
    die \"Invalid interval: [$start, $end]"
        if $start >= $end
        || $exp->evaluate( $var => $start ) * $exp->evaluate( $var => $end )
        > 0;
    return 1;
}

sub iteration_estimate {
    my $self = shift;
    my ( $interv, $e ) = ( $self->end - $self->start, 10**-$self->precision );
    return int( ( _log10($interv) - _log10($e) ) / _log10(2) + 0.5 )
        if $self->validate;
}

sub run {
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( my $var = $self->variable ) or die $exp->getError;
    $exp->setExpression( $self->expression );
    my ( $k, $a, $b )
        = ( $self->iteration_estimate, $self->start, $self->end );
    my $m = $exp->evaluate( $var => $a );
    push @{ $self->partial_results }, { x => $a, y => $m };
    push @{ $self->partial_results },
        { x => $b, y => $exp->evaluate( $var => $b ) };
    my ( $x, $fx );

    for ( 1 .. $k ) {
        $x = ( $a + $b ) / 2;
        $fx = $exp->evaluate( $var => $x );
        push @{ $self->partial_results }, { x => $x, y => $fx };
        my $test = $m * $fx;
        if ( $test > 0 ) {
            $a = $x;
        }
        elsif ( $test == 0 ) {
            last;
        }
        else {
            $b = $x;
        }
    }
    return $x;
}

1;
__END__


=head1 NAME

Math::Calculus::Bisection - Bisection Method

=head1 SYNOPSIS

  use Math::Calculus::Bisection;

  my ($exp, $var) = ('x^2 + x - 6', 'x');
  my ($start, $end, $e) = (1, 3, 14);

  my $method = Math::Calculus::Bisection->new(
     expression => $exp,
     variable   => $var,
     start      => $start,
     end        => $end,
     precision  => $e
  );

  my $k = $method->iteration_estimate;
  my $result = $method->run;
  print $result; #prints 2
  print $k; #prints 48

=head1 DESCRIPTION

Perl extension for numerical finding the roots of equations using bisection method.

=head2 EXPORT

None by default.

=head1 ATTRIBUTES

=head2 expression

Takes an expression in human-readable form and stores it internally as a tree structure, checking it is a valid expression that the module can understand in the process. Note that the module is strict about syntax. For example, note above that you must write 5*x and not just 5x. Whitespace is allowed in the expression, but does not have any effect on precedence. If you require control of precedence, use brackets; bracketed expressions will always be evaluated first, as you would normally expect. The module follows the BODMAS precedence convention. Returns undef on failure and a true value on success. It supports a wide range of expressions including the +, -, *, / and ^ (raise to power) operators, bracketed expressions to enable correct precedence and the functions ln, exp, sin, cos, tan, sec, cosec, cot, sinh, cosh, tanh, sech, cosech, coth, asin, acos, atan, asinh, acosh and atanh.

=head2 variable

A certain named value in the expression as being a variable. A named value must be an alphabetic character.

=head2 interval [start, end]

Range of interval that has at least one root.

=head2 precision

Number of decimal places required in the result.

=head2 partial_results

ArrayRef with partial results.

=head1 METHODS

=head2 new

  my $method = Math::Calculus::Bisection->new(
     expression => $exp,
     variable   => $var,
     start      => $start,
     end        => $end,
     precision  => $e
  );

=head2 validate

=head2 iteration_estimate

Estimate the number of iterations based on the interval and precision.

=head2 run

  my $result = $method->run;

=head1 SEE ALSO

Math::Calculus::Expression, Math::Calculus::NewtonRaphson

=head1 AUTHORS

Wallace Reis E<lt>wreis@cpan.orgE<gt>, Eden Cardim E<lt>edenc@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Eden Cardim & Wallace Reis

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut
